﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Text;
using Fintech.Utils;
using Fintech.Utils.Diagnostics;
using ZOVNP.Model.Abstracts;

namespace ZOVNP.Engine
{
    /// <summary> Класс, реализующий потокобезопасный доступ к навигационным свойствам типа "коллекция" доменных объектов. </summary>
    /// <typeparam name="ImplType"> тип реализации доменного объекта </typeparam>
    /// <typeparam name="InterfaceType"> тип интерфейса доменного объекта </typeparam>
    /// <remarks> поддерживается обработка ошибок с восстановлением соединения с хранилищем (если оно было нарушено) </remarks>
    internal class CollectionLocker<ImplType, InterfaceType> : RelationLocker, IDomainCollection<InterfaceType>
        where ImplType : DomainObject, InterfaceType
        where InterfaceType : class, IDomainObject
    {
        #region Конструкторы

        /// <summary> конструктор с навигационным свойством </summary>
        /// <param name="Owner"> Доменный объект - собственник навигационного свойства (исходящий конец связи). </param>
        /// <param name="Collection"> навигационное свойство "коллекция" </param>
        public CollectionLocker(DomainObject Owner, EntityCollection<ImplType> Collection) : base(Owner, Collection) { }

        #endregion

        #region Свойства

        /// <summary> Получить значение навигационного свойства. </summary>
        /// <remarks> 
        /// Значение навигационного свойства зависит от типа навигации (1 к многим, многие к 1, 1 к 1 и тд)
        /// и может возвращать либо ссылку на единичный объект, либо ссылку на коллекцию объектов. В случае
        /// если возвращается ссылка на объект, то она может иметь значение null. Если же возвращается ссылка
        /// на коллекцию объектов, и эта коллекция пуста, то свойство содержит экземпляр пустой коллекции. 
        /// </remarks>
        public override object Navigation { get { return Navigate().Cast<InterfaceType>(); } }

        #endregion

        #region Методы

        /// <summary> Маркерный вспомогательный метод для множественного попутного извлечения коллекций связанных дочерних объектов внутри одного запроса к родительскому объекту. </summary>
        /// <remarks> Этот метод используется исключительно для трансляции и получения специфического для хранилища текста запроса. Метод имеет пустую реализацию, выбрасывающую исключение. </remarks>
        /// <returns> Ссылка на отдельный элемент коллекции, с которого можно сделать ещё одно обращение к коллекции дочерних объектов. </returns>
        public InterfaceType MarkIncluded() { throw new System.NotSupportedException(); }

        /// <summary> Получить признак того, что коллекция не содержит элементов. </summary>
        /// <returns> True, если коллекция пуста. </returns>
        /// <remarks>
        /// Поведение этого метода зависит от того, загружена ли уже коллекция. Если коллекция загружена и
        /// свойство <c>IsLoaded</c> равно <c>true</c>, то проверяется число загруженных элементов. Если же
        /// коллекция ещё не загружалась и свойство <c>IsLoaded</c> равно <c>false</c>, то к хранилищу данных
        /// направляется запрос на получение числа элементов коллекции без их загрузки.
        /// </remarks>
        public bool IsEmpty() { return (IsLoaded ? (relation as EntityCollection<ImplType>).Count : GetCount()) == 0; }

        /// <summary> Получить число элементов в коллекции. </summary>
        /// <remarks>
        /// Поведение этого метода зависит от того, загружена ли уже коллекция. Если коллекция загружена и
        /// свойство <c>IsLoaded</c> равно <c>true</c>, то проверяется число загруженных элементов. Если же
        /// коллекция ещё не загружалась и свойство <c>IsLoaded</c> равно <c>false</c>, то к хранилищу данных
        /// направляется запрос на получение числа элементов коллекции без их загрузки.
        /// </remarks>
        public int GetCount()
        {
            var casted = relation as EntityCollection<ImplType>;
            bool loaded = casted.IsLoaded || !(Owner.EntityState == System.Data.EntityState.Unchanged || Owner.EntityState == System.Data.EntityState.Modified);
            if (loaded) return casted.Count;
            Owner.GuardContextNeeded();
            return new ZOVNPObjectQuery<InterfaceType, ImplType>((ObjectQuery)((IRelatedEnd)relation).CreateSourceQuery(), false).Count();
            //return ((SkifBPObjectQuery<InterfaceType, ImplType>)Owner.Context.Query<InterfaceType>()).GetCount("it." + Owner.GetType().Name + ".ID = @id",
            //    new System.Data.Objects.ObjectParameter("id", ((Fintech.Utils.IKeyable)Owner).ID));
        }

        /// <summary> осуществить навигацию на присоединённую коллекцию объектов </summary>
        /// <returns> коллекция присоединённых доменных объектов </returns>
        private IEnumerable<ImplType> Navigate()
        {
            TryLoadNavigation(true);
            return relation as IEnumerable<ImplType>;
        }

        /// <summary> Обработчик для окончания загрузки навигационного свойства. </summary>
        protected override void OnRelationLoaded()
        {
            //TODO [Максимов Иван] нужно протестировать сценарий, когда при повторной загрузке связанные объекты умирают
            foreach (var item in (relation as EntityCollection<ImplType>).ToArray())
            {
                if (item.EntityState == System.Data.EntityState.Detached)
                {
                    DebugTuner.Instance.Break();
                    Remove(item);
                }
            }
            base.OnRelationLoaded();
        }

        public IEnumerator<InterfaceType> GetEnumerator() { return Navigate().Cast<InterfaceType>().GetEnumerator(); }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        public void Add(InterfaceType Item)
        {
            (relation as EntityCollection<ImplType>).Add(Item.CheckOnCast<ImplType>("Item"));
            ((ImplType)Item).Context = Owner.Context;
        }

        /// <summary>
        /// Удалить объект из коллекции. Этот объект перестаёт быть дочерним по отношению к объекту,
        /// владеющему данной коллекцией дочерних объектов.
        /// </summary>
        /// <param name="Item"> Объект, который нужно удалить из коллекции. </param>
        /// <remarks>
        /// The Remove method also deletes the relationship between the source object and the object
        /// being removed from the collection. If the relationship has a referential integrity constraint,
        /// calling the Remove  method on a dependent object marks both the relationship and the dependent
        /// object for deletion. This occurs because the constraint indicates that the dependent object
        /// cannot exist without a relationship to the parent. For more information, see Referential
        /// Constraints (Entity Framework).
        /// </remarks>
        public void Remove(InterfaceType Item) { (relation as EntityCollection<ImplType>).Remove(Item.CheckOnCast<ImplType>("Item")); }

        /// <summary> Удалить все объекты из коллекции. </summary>
        /// <remarks> Результат выполнения этого метода эквивалентен вызову метода <c>Remove</c> над всеми сущностями коллекции. </remarks>
        public void RemoveAll()
        {
            if (!IsLoaded) Load();
            var entitiesToRemove = this.ToArray();
            foreach (var entity in entitiesToRemove) Remove(entity);
        }

        /// <summary> Очистить коллекцию. </summary>
        /// <remarks>
        /// Метод выполняет следующие действия.
        /// 1. Устанавливает флаг IsLoaded в значение false.
        /// 2. Удаляет все сущности из коллекции.
        /// 3. Detaches relationships between removed entities and the owner of the EntityCollection from the ObjectStateManager.
        /// 4. Removes the owner of the EntityCollection from the related entities. 
        /// </remarks>
        public void Clear() { (relation as EntityCollection<ImplType>).Clear(); }

        /// <summary> Преобразовать к строке. </summary>
        /// <returns> Строковое представление объекта. </returns>
        public override string ToString()
        {
            return string.Format("Коллекция {0}, {1}.", typeof(ImplType).Name, IsLoaded ? "загружена" : "не загружена");
        }

        #endregion
    }
}
